<?php
/**
 * @Author: gan
 * @Description: 简易版的log日志
 * @File:  Logger
 * @Version: 1.0.0
 * @Date: 2021/12/31 9:50 上午
 */
declare(strict_types=1);

namespace RX\Helpers;

use Psr\Log\LoggerInterface;

class LoggerHelper implements LoggerInterface
{
    public const DEBUG = 100;

    public const INFO = 200;

    public const NOTICE = 250;

    public const WARNING = 300;

    public const ERROR = 400;

    public const CRITICAL = 500;

    public const ALERT = 550;

    public const EMERGENCY = 600;

    protected static array $levels = [
        self::DEBUG     => 'DEBUG',
        self::INFO      => 'INFO',
        self::NOTICE    => 'NOTICE',
        self::WARNING   => 'WARNING',
        self::ERROR     => 'ERROR',
        self::CRITICAL  => 'CRITICAL',
        self::ALERT     => 'ALERT',
        self::EMERGENCY => 'EMERGENCY',
    ];
    protected string       $stream;

    public const SIMPLE_FORMAT = "[%datetime%] %level_name%: %message% %context%\n";

    public function __construct(string $stream)
    {
        $this->stream = $stream;
    }


    public function log($level, $message, array $context = []): void
    {
        if (!is_int($level) && !is_string($level)) {
            throw new \InvalidArgumentException('$level is expected to be a string or int');
        }
        $level = static::toMonologLevel($level);
        $this->addRecord($level, (string)$message, $context);
    }


    public function debug($message, array $context = []): void
    {
        $this->addRecord(static::DEBUG, (string)$message, $context);
    }


    public function info($message, array $context = []): void
    {
        $this->addRecord(static::INFO, (string)$message, $context);
    }


    public function notice($message, array $context = []): void
    {
        $this->addRecord(static::NOTICE, (string)$message, $context);
    }

    public function warning($message, array $context = []): void
    {
        $this->addRecord(static::WARNING, (string)$message, $context);
    }

    public function error($message, array $context = []): void
    {
        $this->addRecord(static::ERROR, (string)$message, $context);
    }


    public function critical($message, array $context = []): void
    {
        $this->addRecord(static::CRITICAL, (string)$message, $context);
    }


    public function alert($message, array $context = []): void
    {
        $this->addRecord(static::ALERT, (string)$message, $context);
    }


    public function emergency($message, array $context = []): void
    {
        $this->addRecord(static::EMERGENCY, (string)$message, $context);
    }


    private function addRecord(int $level, string $message, array $context = []): void
    {
        $levelName = static::getLevelName($level);
        $record    = [
            'datetime'   => (new \DateTime())->format("Y-m-d H:i:s.u"),
            'level_name' => $levelName,
            'level'      => $level,
            'message'    => $message,
            'context'    => $context,
        ];
        $dir       = dirname($this->stream);
        FileHelper::createDirectory($dir, 0755);
        file_put_contents($this->stream, $this->format($record), FILE_APPEND | LOCK_EX);
    }


    public static function toMonologLevel($level): int
    {
        if (is_string($level)) {
            if (is_numeric($level)) {
                return intval($level);
            }
            $upper = strtr($level, 'abcdefgilmnortuwy', 'ABCDEFGILMNORTUWY');
            if (defined(__CLASS__ . '::' . $upper)) {
                return constant(__CLASS__ . '::' . $upper);
            }
            throw new \InvalidArgumentException('Level "' . $level . '" is not defined, use one of: ' . implode(', ', array_keys(static::$levels) + static::$levels));
        }

        if (!is_int($level)) {
            throw new \InvalidArgumentException('Level "' . var_export($level, true) . '" is not defined, use one of: ' . implode(', ', array_keys(static::$levels) + static::$levels));
        }

        return $level;
    }


    public static function getLevelName(int $level): string
    {
        if (!isset(static::$levels[$level])) {
            throw new \InvalidArgumentException('Level "' . $level . '" is not defined, use one of: ' . implode(', ', array_keys(static::$levels)));
        }
        return static::$levels[$level];
    }


    public function format(array $record): string
    {
        $output = self::SIMPLE_FORMAT;

        foreach ($record['context'] as $var => $val) {
            if (false !== strpos($output, '%context.' . $var . '%')) {
                $output = str_replace('%context.' . $var . '%', $this->stringify($val), $output);
                unset($record['context'][$var]);
            }
        }

        foreach ($record as $var => $val) {
            if (false !== strpos($output, '%' . $var . '%')) {
                $output = str_replace('%' . $var . '%', $this->stringify($val), $output);
            }
        }
        return $output;
    }

    /**
     * @param mixed $value
     * @return string
     */
    public function stringify($value): string
    {
        return $this->replaceNewlines($this->convertToString($value));
    }

    protected function replaceNewlines(string $str): string
    {
        return str_replace(["\r\n", "\r", "\n"], ' ', $str);
    }

    /**
     * @param mixed $data
     * @return string
     */
    protected function convertToString($data): string
    {
        if (null === $data || is_bool($data)) {
            return var_export($data, true);
        }

        if (is_scalar($data)) {
            return (string)$data;
        }

        return json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    }
}
